package licode.unisop.platform.application.executor;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import licode.unisop.platform.application.exception.PlatErrorCode;
import licode.unisop.platform.application.gateway.SiPlatProviderManager;
import licode.unisop.platform.application.utils.RelationRule;
import licode.unisop.platform.client.api.SiPlatformAuthService;
import licode.unisop.platform.client.api.SiPlatformService;
import licode.unisop.platform.client.info.*;
import licode.unisop.platform.utils.AssetIdGenUtil;
import licode.unisop.provider.api.SiPlatformProvider;
import licode.unisop.provider.conf.SiAssetState;
import licode.unisop.provider.conf.SiAssetType;
import licode.unisop.provider.conf.SiClassifyType;
import licode.unisop.provider.info.*;
import licode.unisop.provider.vo.SiAssetBrief;
import licode.unisop.provider.vo.SiPlatform;
import licode.unisop.provider.vo.SiProps;
import licode.unisop.types.AuthConst;
import licode.unisop.types.event.DomainEventPublish;
import licode.unisop.types.event.LogDomainEvent;
import licode.unisop.types.event.PlatDomainEvent;
import licode.unisop.types.util.IdGenUtil;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;

import javax.annotation.Resource;
import java.util.ArrayList;

/**
 * 平台相关操作接口定义
 *
 * @author licode
 */
@Primary
@Service
public class SiPlatformExecutor implements SiPlatformService {
    @Resource
    private RelationRule relationRule;

    @Resource
    private DomainEventPublish domainEventPublish;

    @Resource
    private SiPlatProviderManager siPlatProviderManager;

    @Resource
    private SiPlatformAuthService siPlatformAuthService;

    public void updateCache(SiPlatformId platId) {
        siPlatformAuthService.updateCache(platId);
    }

    public void createPlatformDefaultAssets(SiPlatformProvider provider, SiPlatform plat) {
        String roleId = IdGenUtil.buildRoleId();
        provider.addAsset(SiSaveAssetIn.builder()
                .platformId(plat.getId())
                .tenantId(plat.getTenantId())
                .brief(SiAssetBrief.builder()
                        .id(roleId)
                        .rootId(roleId)
                        .parentId("")
                        .code(AuthConst.SYSTEM_ADMIN)
                        .name("系统管理员")
                        .type("ROLE")
                        .subType("")
                        .classify("TREE")
                        .state(1)
                        .isSystem(1)
                        .level(0)
                        .build())
                .build());
    }

    @Override
    public void addPlatform(PlatformSaveIn saveIn) {
        LogDomainEvent event;
        SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId(saveIn.getPlatform().getTenantId())
                .platformId("default")
                .build());

        provider.addPlatform(saveIn.getPlatform());

        // 为平台生成默认资源
        createPlatformDefaultAssets(provider, saveIn.getPlatform());

        event = new LogDomainEvent(JSON.toJSONString(saveIn.getPlatform()), "plat:add");
        event.setTokenInfo(saveIn.getAuthIn());

        domainEventPublish.publish(event);
        domainEventPublish.publish(new PlatDomainEvent(saveIn.getPlatform().getId(),
                event.getData(), event.getEventType()));
    }

    @Override
    public void updatePlatform(PlatformSaveIn saveIn) {
        findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId(saveIn.getPlatform().getTenantId())
                .platformId("default")
                .build()).updatePlatform(saveIn.getPlatform());
        LogDomainEvent event;

        event = new LogDomainEvent(JSON.toJSONString(saveIn.getPlatform()), "plat:update");
        event.setTokenInfo(saveIn.getAuthIn());

        domainEventPublish.publish(event);
        domainEventPublish.publish(new PlatDomainEvent(saveIn.getPlatform().getId(),
                event.getData(), event.getEventType()));
    }

    @Override
    public void updatePlatformRealm(SiPlatRealmUpdateIn platform) {
        if (null != platform.getPlatformId() && !platform.getPlatformId().isEmpty()) {
            String realmId = null == platform.getRealmId() ? "" : platform.getRealmId();
            SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                    .builder()
                    .tenantId("")
                    .platformId(platform.getPlatformId())
                    .build());
            SiPlatform plat = provider.findPlatform(SiPlatIdFind.builder()
                    .platformId(platform.getPlatformId()).build());
            if (null != plat && !realmId.equals(plat.getRealmId())) {
                provider.removeRelAsset(SiRemoveRelAsset.builder()
                        .platformId(platform.getPlatformId())
                        .assetType(SiAssetType.AT_USER)
                        .build());
                provider.updatePlatformRealm(platform);
                domainEventPublish.publish(new PlatDomainEvent(platform.getPlatformId(),
                        "", "asset:updatePlatformRealm"));
            }
        }
    }

    @Override
    public void grantPlatform(SiAuthPlatToUserIn item) {
        SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId("")
                .platformId("default")
                .build());
        provider.grantPlatform(item);
    }

    public void saveAndUpdateAttributes(SiPlatformProvider provider,
                                        SiSaveAssetIn assetIn, SiAssetBrief existBrief) {
        String code = existBrief.getCode();
        if (null != code && AuthConst.SYSTEM_ADMIN.compareToIgnoreCase(code) == 0) {
            throw PlatErrorCode.PLATFORM_CANT_EDIT_SYSTEM_ASSET.buildThrow();
        }

        if (!SiAssetState.AT_ACTIVATE.equals(assetIn.getBrief().getState())) {
            if (AuthConst.IS_SYSTEM.equals(existBrief.getIsSystem())) {
                throw PlatErrorCode.PLATFORM_CANT_DEL_SYSTEM_ASSET.buildThrow();
            }
        }
        if (null == existBrief.getProps()) {
            provider.updateAsset(assetIn);
        } else {
            if (null == assetIn.getBrief().getProps()) {
                assetIn.getBrief().setProps(SiProps.build(existBrief.getProps()));
            } else {
                existBrief.getProps().forEach((key, val) -> {
                    if (!assetIn.getBrief().getProps().contain(key)) {
                        assetIn.getBrief().getProps().addProp(key, val);
                    }
                });
            }
            provider.updateAsset(assetIn);
        }
    }

    public void handleClientAsset(SiAssetBrief brief) {
        if (SiAssetType.AT_CLIENT.equals(brief.getType())) {
            SiProps props = brief.getProps();
            brief.setClassify(SiClassifyType.ART_HOLD);

            if (null != props) {
                int accessTimeout = props.getInt("accessTimeout", 300);
                int refreshTimeout = props.getInt("refreshTimeout", 0);

                if (accessTimeout <= 0) {
                    accessTimeout = 300;
                }

                if (refreshTimeout <= accessTimeout) {
                    refreshTimeout = accessTimeout + 300;
                }

                props.removeProp("accessTimeout");
                props.removeProp("refreshTimeout");
                props.addProp("accessTimeout", accessTimeout);
                props.addProp("refreshTimeout", refreshTimeout);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void saveAsset(PlatSaveAssetIn assetIn) {
        LogDomainEvent event;
        SiAssetBrief codeBrief = null;
        SiAssetBrief existBrief = null;
        String platId = assetIn.getAssetIn().getPlatformId();

        SiAssetBrief brief = assetIn.getAssetIn().getBrief();
        SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId(assetIn.getAssetIn().getTenantId())
                .platformId(platId)
                .build());

        // 处理客户端参数
        handleClientAsset(brief);

        if (StrUtil.isNotBlank(brief.getId())) {
            existBrief = provider.lookupAssetBrief(SiAssetLookupIn.builder()
                    .ownerId(platId)
                    .assetId(brief.getId())
                    .assetType(brief.getType())
                    .build());
        }

        if (StrUtil.isBlank(brief.getId())) {
            brief.setId(AssetIdGenUtil.buildId(brief.getType()));
        }

        if (AuthConst.DEFAULT_ID.equals(brief.getId())) {
            brief.setState(SiAssetState.AT_ACTIVATE);
        }

        if (StrUtil.isNotBlank(brief.getCode())) {
            codeBrief = provider.lookupAsseByCode(SiLookupAssetByCodeIn.builder()
                    .platformId(platId)
                    .assetType(brief.getType())
                    .code(brief.getCode())
                    .build());
        }

        if (null == existBrief) {
            if (null != codeBrief) {
                throw PlatErrorCode.PLATFORM_ASSET_CODE_EXIST.buildThrow();
            }
            if (null == assetIn.getAssetIn().getBrief().getIsSystem()) {
                assetIn.getAssetIn().getBrief().setIsSystem(0);
            }
            provider.addAsset(assetIn.getAssetIn());
            event = new LogDomainEvent(JSON.toJSONString(assetIn.getAssetIn()), "asset:add");
        } else {
            if (null != codeBrief && !codeBrief.getId().equals(brief.getId())) {
                throw PlatErrorCode.PLATFORM_ASSET_CODE_EXIST.buildThrow();
            }

            saveAndUpdateAttributes(provider, assetIn.getAssetIn(), existBrief);
            event = new LogDomainEvent(JSON.toJSONString(assetIn.getAssetIn()), "asset:update");
        }

        event.setTokenInfo(assetIn.getAuthIn());

        domainEventPublish.publish(event);
        domainEventPublish.publish(new PlatDomainEvent(platId, event.getData(), event.getEventType()));
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void removeAttrOfAsset(PlatRemoveAssetAttributeIn req) {
        SiRemoveAttrOfAssetIn removeInfo = req.getAttrIn();
        SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId(removeInfo.getTenantId())
                .platformId(removeInfo.getPlatformId())
                .build());
        SiAssetBrief brief = provider.lookupAssetBrief(SiAssetLookupIn.builder()
                .ownerId(removeInfo.getPlatformId())
                .assetId(removeInfo.getAssetId())
                .assetType(removeInfo.getAssetType())
                .build());
        if (null != brief && null != removeInfo.getProps()) {
            removeInfo.getProps().forEach(item -> brief.getProps().removeProp(item));
            provider.updateAsset(SiSaveAssetIn.builder()
                    .platformId(removeInfo.getPlatformId())
                    .tenantId(removeInfo.getTenantId())
                    .brief(brief)
                    .build());
        }
    }

    @Override
    public void saveHoldAssets(PlatSaveHoldAssetsIn req) {
        SiSaveHoldAssets assetIn = req.getAssetIn();
        String platId = assetIn.getPlatformId();

        SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId(assetIn.getTenantId())
                .platformId(assetIn.getPlatformId())
                .build());

        if ("USER".equals(assetIn.getHoldType())) {
            SiPlatform platform = provider.findPlatform(SiPlatIdFind.builder()
                    .platformId(platId).build());
            assetIn.setHoldOwner(platform.getRealmId());
        } else {
            assetIn.setHoldOwner(platId);
        }
        assetIn.setAssetOwner(platId);

        if (relationRule.isCanHold(assetIn.getHoldType(), assetIn.getAssetType())) {
            throw PlatErrorCode.PLATFORM_ASSET_WRONG_RULE.buildThrow();
        }

        provider.saveHoldAssets(assetIn);

        domainEventPublish.publish(new PlatDomainEvent(platId,
                "", "asset:saveHoldAsset"));
    }

    @Override
    public void removeRelAsset(PlatRemoveRelAssetIn req) {
        SiRemoveRelAsset relAsset = req.getRelAsset();
        String platId = relAsset.getPlatformId();

        if (relAsset.getAssetIds() == null) {
            relAsset.setAssetIds(new ArrayList<>());
        }

        if (relAsset.getTargetIds() == null) {
            relAsset.setTargetIds(new ArrayList<>());
        }

        if (StrUtil.isBlank(relAsset.getPlatformId())) {
            throw PlatErrorCode.PLATFORM_ID_DONT_EMPTY.buildThrow();
        }

        if (StrUtil.isBlank(relAsset.getAssetType()) &&
                StrUtil.isBlank(relAsset.getAssetId()) &&
                relAsset.getAssetIds().isEmpty()) {
            throw PlatErrorCode.PLATFORM_REMOVE_ASSET_PARAM_ERROR.buildThrow();
        }

        findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId("")
                .platformId(relAsset.getPlatformId())
                .build()).removeRelAsset(relAsset);

        domainEventPublish.publish(new PlatDomainEvent(platId,
                "", "asset:removeHoldAsset"));
    }

    @Override
    public void removeAllRelAsset(SiRemoveAllRelAsset req) {
        findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId("")
                .platformId(req.getPlatformId())
                .build()).removeAllRelAsset(req);

        domainEventPublish.publish(new PlatDomainEvent(req.getPlatformId(),
                "", "asset:removeRelAsset"));
    }

    @Override
    public void saveAssetHolds(PlatSaveAssetHoldsIn req) {
        SiSaveAssetHolds holdIn = req.getHoldIn();
        SiPlatformProvider provider = findPlatformPlugin(SiPlatIdFind
                .builder()
                .tenantId(holdIn.getTenantId())
                .platformId(holdIn.getPlatformId())
                .build());

        holdIn.setAssetOwner(holdIn.getPlatformId());

        if ("USER".equals(holdIn.getHoldType())) {
            SiPlatform platform = provider.findPlatform(SiPlatIdFind.builder()
                    .platformId(holdIn.getPlatformId()).build());
            holdIn.setHoldOwner(platform.getRealmId());
        } else {
            holdIn.setHoldOwner(holdIn.getPlatformId());
        }

        if (relationRule.isCanHold(holdIn.getHoldType(), holdIn.getAssetType())) {
            throw PlatErrorCode.PLATFORM_ASSET_WRONG_RULE.buildThrow();
        }

        provider.saveAssetHolds(holdIn);

        domainEventPublish.publish(new PlatDomainEvent(holdIn.getPlatformId(),
                "", "asset:saveAssetHolds"));
    }

    private SiPlatformProvider findPlatformPlugin(SiPlatIdFind info) {
        return siPlatProviderManager.findPlatProvider(info);
    }
}
